Components.utils.import("resource://handytab/datastore.jsm");

const FENNEC_APP_ID = "{a23983c0-fd0e-11dc-95ff-0800200c9a66}"

var handytab = {
  mobileMode: false,
  prefs: null,
  homepage: "chrome://handytab/locale/handytab.html",
  autosave: false,
  markKey: 'x',
  invertKey: 'z',
  markKeyDown: false,
  invertKeyDown: false,
  markMode: false,
  
  prefListener : {
    register: function() {
      var branch = handytab.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
      branch.addObserver("", this, false);
    },
    
    observe: function(subject, topic, data) {
      if (topic != "nsPref:changed") return;
      switch (data) {
        case "show.closebtn":
          var tab = handytab.findTab(false, false);
          if (tab) handytab.updateCloseButton(tab);
          break;
        case "homepage":
          handytab.homepage = handytab.prefs.getCharPref("homepage");
          break;
        case "mark.key":
          handytab.markKey = handytab.prefs.getCharPref("mark.key");
          break;
      }
    }
  },

  sendMarksToHtml: function(doc, all) {
    var element = doc.getElementById("handytab-api");

    var marks = HandyTabDataStore.getMarks(all);
    element.setAttribute("data", marks);

    var evt = doc.createEvent("Events");
    evt.initEvent("HandyTabHtmlEvent", true, false);
    element.dispatchEvent(evt);
  },
  
  onHandyTabAddonEvent: function(e) {
    if (e.target.ownerDocument.location != this.homepage) return;
    
    var subject = e.target.getAttribute("subject");
    switch (subject) {
      case "fetch-rows":
        this.sendMarksToHtml(e.target.ownerDocument, true);
        break;
      case "remove-rows":
        var args = e.target.getAttribute("args");
        HandyTabDataStore.removeMarks(args);
        break;
    }
  },

  log: function(msg) {
    var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                                   .getService(Components.interfaces.nsIConsoleService);
    consoleService.logStringMessage('handytab: ' + msg);
  },
  
  catchLinkClick: function() {
    var tab = this.mobileMode ? Browser.selectedTab.chromeTab : gBrowser.selectedTab;
    if (tab.hasAttribute("handytab-hooked")) return;
    tab.setAttribute("handytab-hooked", "true");
    
    var browser = this.mobileMode ? Browser.selectedBrowser : gBrowser.getBrowserForTab(tab);
    
    browser.addEventListener("keydown", function(e) {
      var key = String.fromCharCode(e.which).toLowerCase();
      if (handytab.markKey.toLowerCase() == key) {
        handytab.markKeyDown = true;
      }
      else if (handytab.invertKey.toLowerCase() == key) {
        handytab.invertKeyDown = true;
      }
    }, false);

    browser.addEventListener("keyup", function(e) {
      var key = String.fromCharCode(e.which).toLowerCase();
      if (handytab.markKey.toLowerCase() == key) {
        handytab.markKeyDown = false;
      }
      else if (handytab.invertKey.toLowerCase() == key) {
        handytab.invertKeyDown = false;
      }
    }, false);
    
    browser.addEventListener("click", function(e) {
      if (e.button != 0 || e.ctrlKey || e.altKey || e.shiftKey) return;
      if (e.target.tagName == "A" || e.target.tagName == "IMG" || e.target.parentNode.tagName == "A") {
        if (((handytab.markMode && !handytab.invertKeyDown) || (!handytab.markMode && handytab.invertKeyDown)) &&
            e.target.ownerDocument.location.toString() != handytab.homepage) {
          handytab.addMark(e.target);
          e.preventDefault();
          e.stopPropagation();
        }
      } else if (handytab.markKeyDown) {
        handytab.toogleMarkMode();
      }
    }, true);
  },
  
  onLoad: function() {
    this.initialized = true;
    this.strings = document.getElementById("handytab-strings");

    var info = Components.classes["@mozilla.org/xre/app-info;1"]  
                         .getService(Components.interfaces.nsIXULAppInfo);  
    if (info.ID == FENNEC_APP_ID) this.mobileMode = true;
    
    var prefSrv = Components.classes["@mozilla.org/preferences-service;1"]
                            .getService(Components.interfaces.nsIPrefService);
    this.prefs = prefSrv.getBranch("extensions.handytab.options.");

    this.prefListener.register();

    HandyTabDataStore.init();

    this.autosave = this.prefs.getBoolPref("autosave");
    if (this.autosave) HandyTabDataStore.load();
    
    this.markKey = this.prefs.getCharPref("mark.key");
    this.invertKey = this.prefs.getCharPref("invert.key");
    
    this.catchLinkClick();
    
    document.addEventListener("HandyTabAddonEvent", function(e) { handytab.onHandyTabAddonEvent(e); }, false, true);

    if (this.mobileMode) {
      var tabs = document.getElementById("tabs");
      tabs.addEventListener("TabSelect", function(e) { handytab.onTabSelect(e); }, false);
    } else {
      var tabs = gBrowser.tabContainer;
      tabs.addEventListener("SSTabRestored", function(e) { handytab.onSSTabRestored(e); }, false);
      tabs.addEventListener("TabSelect", function(e) { handytab.onTabSelect(e); }, false);
    }
  },

  onUnload: function() {
    if (!this.mobileMode) {
      var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                         .getService(Components.interfaces.nsIWindowMediator);
      var enumerator = wm.getEnumerator("navigator:browser");
      var count = 0;
      while (count < 2 && enumerator.hasMoreElements()) {
        var win = enumerator.getNext();
        count++;
      }
      if (count == 2) return;
    }
    
    var autosave = this.prefs.getBoolPref("autosave");

    if (autosave) HandyTabDataStore.save();
    else if (this.autosave) HandyTabDataStore.reset();
  },
  
  updateCloseButton: function(tab) {
    var showbtn = this.prefs.getBoolPref("show.closebtn");
    if (!showbtn) tab.setAttribute("hide-close-button", true);
    else if (tab.hasAttribute("hide-close-button")) tab.removeAttribute("hide-close-button");
  },
  
  makeHandyTab: function(tab) {
    tab.setAttribute("handytab", "true");
    tab.setAttribute("flex", 0);
    tab.setAttribute("minwidth", 0);

    this.updateCloseButton(tab);
    
    var firsttab = this.prefs.getBoolPref("show.firsttab");
    if (firsttab && gBrowser.tabContainer.itemCount != 1) gBrowser.moveTabTo(tab, 0);
  },
  
  onSSTabRestored: function(e) {
    var tab = e.originalTarget;
    var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
                       .getService(Components.interfaces.nsISessionStore);
    var value = ss.getTabValue(tab, "handytab");
    if (value) this.makeHandyTab(tab);
  },
  
  onTabSelect: function(e) {
    this.catchLinkClick();

    if (this.mobileMode) {
      var tab = Browser.selectedTab;
      if (!tab.chromeTab.hasAttribute("handytab")) return;
      this.sendMarksToHtml(tab.browser.contentDocument, false);
      return;
    }
    
    var tab = gBrowser.selectedTab;
    if (!tab.hasAttribute("handytab")) return;
    
    var browser = gBrowser.getBrowserForTab(tab);
    this.sendMarksToHtml(browser.contentDocument, false);
  },
  
  findTabInTabBrowser: function(tabbrowser) {
    var numTabs = tabbrowser.browsers.length;

    for (var index = 0; index < numTabs; index++) {
      var tab = tabbrowser.tabContainer.childNodes[index];
      if (tab.hasAttribute("handytab")) return tab;
    }

    return null;
  },
  
  findTab: function(allwin, show) {
    if (this.mobileMode) {
      for (var i = 0; i < Browser._tabs.length; i++) {
        var tab = Browser._tabs[i];
        if (tab.chromeTab.hasAttribute("handytab")) {
          if (show) Browser.selectedTab = tab;
          return tab;
        }
      }
      return null;
    }
    
    if (!allwin) {
      var tab = this.findTabInTabBrowser(gBrowser);
      if (tab && show) gBrowser.selectedTab = tab;
      return tab;
    }
    
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                       .getService(Components.interfaces.nsIWindowMediator);
    var browserEnumerator = wm.getEnumerator("navigator:browser");

    while (browserEnumerator.hasMoreElements()) {
      var browserWin = browserEnumerator.getNext();
      var tabbrowser = browserWin.gBrowser;
      
      var tab = this.findTabInTabBrowser(tabbrowser);
      if (tab) {
        if (show) {
          tabbrowser.selectedTab = tab;
          browserWin.focus();
        }
        return tab;
      }
    }

    return null;
  },
  
  showTab: function() {
    var tab = this.findTab(true, true);
    if (tab) return;
    
    if (this.mobileMode) {
      var tab = Browser.addTab(this.homepage, true);
      tab.chromeTab.setAttribute("handytab", "true");
      return;
    }
    
    tab = gBrowser.addTab(this.homepage);
  
    this.makeHandyTab(tab);
    
    var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
                       .getService(Components.interfaces.nsISessionStore);
    ss.setTabValue(tab, "handytab", true);
    
    gBrowser.selectedTab = tab;
  },

  // Gather all descendent text under given document node.
  gatherTextUnder: function(root) 
  {
    var text = "";
    var node = root.firstChild;
    var depth = 1;
    while ( node && depth > 0 ) {
      // See if this node is text.
      if ( node.nodeType == Node.TEXT_NODE ) {
        // Add this text to our collection.
        text += " " + node.data;
      } else if ( node instanceof HTMLImageElement) {
        // If it has an alt= attribute, use that.
        var altText = node.getAttribute( "alt" );
        if ( altText && altText != "" ) {
          text = altText;
          break;
        }
      }
      // Find next node to test.
      // First, see if this node has children.
      if ( node.hasChildNodes() ) {
        // Go to first child.
        node = node.firstChild;
        depth++;
      } else {
        // No children, try next sibling.
        if ( node.nextSibling ) {
          node = node.nextSibling;
        } else {
          // Last resort is our next oldest uncle/aunt.
          node = node.parentNode.nextSibling;
          depth--;
        }
      }
    }
    // Strip leading whitespace.
    text = text.replace( /^\s+/, "" );
    // Strip trailing whitespace.
    text = text.replace( /\s+$/, "" );
    // Compress remaining whitespace.
    text = text.replace( /\s+/g, " " );
    return text;
  },
  
  getLinkText: function(link) {
    var text = this.gatherTextUnder(link);
    if (!text.match(/\S/)) text = null;
    if (!text) {
      text = link.getAttribute("title");
      if (!text.match(/\S/)) text = null;
      if (!text) {
        text = link.getAttribute("alt");
        if (!text.match(/\S/)) text = null;
      }
    }
    return text;
  },
  
  markPage: function() {
    var tab = this.mobileMode ? Browser.selectedTab.chromeTab : gBrowser.selectedTab;
    var browser = this.mobileMode ? Browser.selectedBrowser : gBrowser.getBrowserForTab(tab);
    this.addMark(browser.contentDocument.body);
  },
  
  markLink: function(e) {
    if (!gContextMenu || !gContextMenu.target) return;

    this.addMark(gContextMenu.target);
  },
  
  addMark: function(target) {
    var url;
    var linktext;
    
    if (target.tagName == "A") {
      url = target.href;
      linktext = this.getLinkText(target);
      if (!linktext) linktext = target.href;
    } else if (target.tagName == "IMG") {
      if (target.parentNode.tagName == "A") {
        this.addMark(target.parentNode);
        return;
      }
      url = target.src;
      linktext = target.getAttribute("alt");
      if (!linktext) linktext = url;
    } else if (target.parentNode.tagName == "A") {
      this.addMark(target.parentNode);
      return;
    } else {
      url = target.ownerDocument.location.toString();
      linktext = target.ownerDocument.title;
    }

    var refer = target.ownerDocument.location.toString();
    
    var faviconService = Components.classes["@mozilla.org/browser/favicon-service;1"]
                                   .getService(Components.interfaces.nsIFaviconService);
    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);

    var icon = "";
    var uri = ioService.newURI(refer, null, null);
    try {
      var faviconURI = faviconService.getFaviconForPage(uri);
      icon = faviconURI.spec;
    } catch (err) {}

    var mark = {linktext: linktext, url: url, refer: refer, icon: icon, time: new Date()};
    HandyTabDataStore.addMark(mark);
    
    var notify = this.prefs.getBoolPref("mark.notify");
    
    if (!notify) return;

    var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
                                  .getService(Components.interfaces.nsIAlertsService);
    alertsService.showAlertNotification("chrome://handytab/skin/handytab.png",
                                        this.strings.getString("extensions.handytab.msg.marked"),
                                        url,
                                        true);
  },
  
  toogleMarkMode: function() {
    this.markMode = !this.markMode;
    if (this.mobileMode) {
      var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
                                    .getService(Components.interfaces.nsIAlertsService);
      var msg = this.markMode ? this.strings.getString("extensions.handytab.msg.markmode.on") :
                                this.strings.getString("extensions.handytab.msg.markmode.off");
      alertsService.showAlertNotification("chrome://handytab/skin/handytab.png",
                                          msg,
                                          null,
                                          true);
    } else {
      var statusicon = document.getElementById("handytab-status");
      if (this.markMode) statusicon.setAttribute("markmode", true);
      else statusicon.removeAttribute("markmode");
    }
  }
};

window.addEventListener("load", function(e) { handytab.onLoad(e); }, false);
window.addEventListener("unload", function(e) { handytab.onUnload(e); }, false);
